/* 
 * File:   Engine.cpp
 * Author: Gab'z
 * 
 * Created on 27 mai 2012, 20:04
 */

#include "Engine.h"
#include <string>
#include "../model/command/Command.h"
#include "../tool/trim.h"
#include "../exception/ParseException.h"
#include "tool/toupper.h"
#include "../model/parameters/Parameters.h"
#include "../model/command/parameters/AllParametersCommand.h"

namespace calculator {

    Logger* Engine::log = LoggerManager::getInstance()->getLogger("Engine");

    Engine::Engine(Context * context) :
    _context(context),
    _calculatorParser(new CalculatorParser(context)),
    _commandManager(new CommandManager(this, context->getMementoCaretaker())) {
    }

    const Context * const Engine::getContext() const {
        return _context;
    }

    Engine::~Engine() {
        delete _calculatorParser;
        delete _commandManager;
    }

    void Engine::setInputText(const std::string& str) const {
        _context->setInputString(str);
    }

    void Engine::undo() const {
        if (_context->getMementoCaretaker()->hasPrevious()) {
            run("UNDO");
        } else {
            log->error("UNDO command called too early");
        }
    }

    void Engine::redo() const {
        if (_context->getMementoCaretaker()->hasNext()) {
            run("REDO");
        } else {
            log->error("REDO command called too early");
        }
    }

    void Engine::saveAllParameters(const Parameters* parameters) const {
        log->trace("saveAllParameters...");
        const Command* saveAllParametersCommand = new AllParametersCommand(parameters);
        try {
            _commandManager->executeCommand(saveAllParametersCommand);
        } catch (CommandException& e) {
            log->error(e.what());
        }
        delete saveAllParametersCommand;
        log->trace("saveAllParameters done");
    }

    void Engine::newStack() const {
        run("SNEW");
    }

    void Engine::dupStack() const {
        run("SDUP");
    }

    void Engine::setCurrentStack(int i) const {
        if (i >= 0 && i < (int) _context->getStackList()->size()) {
            std::ostringstream oss;
            oss << i << " SWITCH";
            run(oss.str());
        } else {
            log->error("SWITCH to Stack out of stackList");
        }
    }

    void Engine::removeCurrentStack() const {
        if (_context->getStackList()->size() > 1) {
            run("SREM");
        } else {
            log->error("SREM command called with only one stack");
        }
    }

    void Engine::setConstantTypeTo(const std::string& constantType) const {
        run(constantType);
    }

    void Engine::setAngleUnitTo(const std::string& angleUnit) const {
        run(angleUnit);
    }

    void Engine::switchComplexMode() const {
        run("COMPLEX");
    }

    void Engine::drop() const {
        run("DROP");
    }

    void Engine::run(const std::string byPassCommand) const {
        try {
            log->trace("Engine.run()");
            //_context->setInputString(_interface->getInputString());
            _context->setError(false);
            // on veut executer ce qu'il y a dans la ligne de saisie
            // si elle est vide,
            //      on appelle la commande DUP
            // sinon
            //      on execute de gauche a droite les commandes saisies
            // on affiche ensuite dans la ligne de saisie 
            std::string commandString;
            if (byPassCommand != "") {
                commandString = byPassCommand;
            } else {
                commandString = toUpper(trim(_context->getInputString()));
            }
            std::string returnString = "";
            std::string resultExpressionExecution;
            if (commandString.empty()) {
                log->trace(" Empty input => using DUP");

                // récupération (d'une copie) et exécution de la commande DUP
                const Command* dupCommand = _context->getCommandMap()->getCommand("DUP");
                try {
                    resultExpressionExecution = _commandManager->executeCommand(dupCommand);
                } catch (CommandException& e) {
                    //notify(); 
                    std::string str = "   ";
                    log->error(str + e.what());
                } catch (std::exception& e) {
                    std::string str = "   ";
                    log->error(str + e.what());
                }
                delete dupCommand;
                if (!Context::getInstance()->getCurrentStack()->empty()) {
                    returnString = Context::getInstance()->getCurrentStack()->top()->toString();
                }
            } else {
                // la ligne contient au moins une instruction
                log->debug(" START running while() loop");
                while (!commandString.empty() && _context->getError() == false) {
                    log->debug("  Processing input...");

                    std::string commandWord = "";
                    // ISOLEMENT de la 1ère commande
                    trim(commandString);

                    log->trace("  Reading input...");
                    // <editor-fold defaultstate="collapsed" desc="ISOLEMENT CONSTANTE">
                    // Il peut s'agir d'une expression
                    // elle commence par '
                    // on doit trouver un autre '
                    // représente tout command word mais nécessite d'être parsé différemment
                    int expStart = commandString.find("\'");
                    if (expStart == 0) {
                        // ca ressemble à une expression
                        std::size_t expEnd = commandString.find("\'", 1);
                        if (expEnd == std::string::npos) {
                            // expression mal écrite, on affichera une erreur
                            _context->setError();
                            break; // on arrete la boucle pour ne pas parser
                        }
                        // recopie de l'expression avec ' '
                        commandWord = commandString.substr(0, expEnd + 1);
                    } else {

                        //pas une expression
                        // recherche d'un mot terminé soit par la fin de la ligne,
                        // soit par un espace
                        std::size_t spacePosition = commandString.find(" ");
                        if (spacePosition != std::string::npos) {
                            // espace trouvé on prend le mot
                            commandWord = commandString.substr(0, spacePosition);
                        } else {
                            // pas d'espace on prend toute la ligne
                            commandWord = commandString;
                        }
                    }

                    // </editor-fold>
                    log->trace("  Read input done");

                    log->trace("  Parsing \"" + commandWord + "\"...");
                    // <editor-fold defaultstate="collapsed" desc="PARSING">
                    // PARSING 
                    const Command* command;
                    try {
                        log->trace("   Parsing ... try");
                        command = _calculatorParser->parse(commandWord);
                        log->trace("   Parsing ... OK");

                    } catch (ParseException& e) {
                        // échec parsing
                        // 2 CAS D'UTILISATION
                        // instantCompute = true
                        // échec involontaire => ne pas afficher d'erreur

                        // échec suite à demande utilisateur
                        // => afficher erreur
                        //                        if (Context::getInstance()->getParameters()->getInstantCompute() == false) {
                        _context->setError();
                        //                        }
                        log->debug("   sortie de la boucle while du PARSING");
                        break;
                    }
                    log->trace("  Parsing done");

                    // parsing reussi
                    // poursuite normale
                    // </editor-fold>
                    log->debug("  Parse successful, command is : " + command->toString());

                    log->trace("  Executing command : " + command->toString() + "...");

                    // <editor-fold defaultstate="collapsed" desc="EXECUTION">
                    // EXECUTION

                    /**
                     * 
                     * resultExpressionExecution = _commandManager->executeCommand(command);
                     * resultExpressionExecution = "" si la commande s'est bien déroulée
                     * resultExpressionExecution = "contenu non évalué d'une expression " si suite à un eval avec problème
                     * 
                     */

                    try {
                        resultExpressionExecution = _commandManager->executeCommand(command);
                    } catch (CommandException& e) {
                        std::string str = "   ";
                        log->error(str + e.what());
                    }

                    delete command;
                    if (resultExpressionExecution == "") {
                        
                        // la commande s'est bien executée
                        // on renverra comme string la dernière commande 
                        // bien executée et sauvegardée si cela continue de bien se passer
                        // (enregistrée dans le dernier Memento durant executeCommand)
                        if (returnString == "") {
                            // on ne réalise cet accès qu'une seule fois dans la boucle while
                            // peut être vide si action non annulable (UNDO ou REDO)
                            returnString = _commandManager->getPreviousInputString();
                        }

                        // on retire le mot évalué de la string manipulée
                        commandString = commandString.substr(commandWord.size());


                    } else {
                        // échec évalutaion d'une expression
                        // on renvoie comme string :
                        // contenu non évalué de l'expression 
                        // + 
                        // restant de inputString non parsé
                        returnString = resultExpressionExecution + " " + commandString;
                        log->trace("ERREUR CAPTEE");
                        _context->setError();
                        // sortie de la boucle while()
                        break;
                    }// </editor-fold>

                    log->debug("  Processing input done");
                }
                log->debug(" END running while() loop");

                // inputString exploitée

                // fin de l'exécution
                _context->setInputString(returnString);
                log->trace("Engine.run() done");
            }

            int nbConst = _context->getCurrentStack()->size();
            std::ostringstream oss;
            oss << "CurrentStack index = " << _context->getStackList()->getCurrentStackIndex();
            log->trace(oss.str());
            while (nbConst > 0) {
                log->trace("CurrentStack : " + _context->getCurrentStack()->getConstant(_context->getCurrentStack()->size() - nbConst)->toString());
                nbConst--;
            }
        } catch (std::exception& e) {
            log->error(e.what());
        }
    }
}
